情境:
Ken:歐嚕,今天午餐妳覺得要吃什麼好呢?
Ken:已經月中了,是不是該開始吃土了勒?
歐嚕:喵喵 喵喵喵 喵
是不是對這段對話,感到匪夷所思呢!?
但這其中其實包含著程式語言內的單一職責與依賴關係… (りしれ供さ小)
請容我詳細介紹
或許大家注意到,昨天我們討論的物件導向,很強調的是訊息,不論是傳遞、接收與處理,作用對象都是訊息。
而Ruby的對象就是從類別所生成的,裡面的每一個方法,都是一個行為,而所回傳的就是訊息!
所以~我們該如何決定類別內有哪些方法呢(Method)?
最簡單的判斷辦法就是問問看物件,這就很像我問歐嚕午餐吃什麼一樣,她不應該有辦法回應這個問題,因為這不該是她的責任(這裡的責任所代表的是合理性的行為而非義務等概念)。
class Cat
def recommend_restaurant
# 為什麼貓會有這個方法!?
end
end
這時候有請我們Google大神,那怎樣設計類別才符合SRP呢?
A class should have only a single responsibility.
A class should have only one reason to change.
而我認為最好理解的則是Sandi Metz書中提的 內聚(Cohesion) 來描述SRP
When everything in a class is related to its central purpose, the class is said to be highly cohesive or the have a single responsibility.
我的認知是,一個類別內所有的方法,都應該符合這類別的核心定位,就以貓類別來說
class Cat
def hunt
end
def have_party_at_night
end
end
不可否認的這兩個動作,都和符合貓的定位,而recommend_restaurant
的方法應該存在於我的家人或者朋友(也可以是google大神)的類別才對。
而我第二個問題,是否要吃土? 這個問題只有我才會知道,所以程式的世界就是~
class Ken
def lack_of_money?
# 計算餘額
# 回傳true或是false
end
end
在Ken類別內有一個方法叫做lack_of_money?
會去計算著我是否有足夠的$$來回傳true或者是false。
那方法的單一責任又是怎麼設計呢?
那就是! 依賴行為而非資料
是不是有點難懂...
首先~ 還記得Ruby提供了attr_resder
的方法嗎? 他直接將變數封裝成為方法。
不過為什麼我們不直接取用變數就好?
其中一項解釋就是方法成為了行為,讓這個行為遵守Don't repeat yourself(DRY)
讓我們回到剛剛的例子
class Ken
attr_resder :money
def initialize(money)
@money = money
end
def lack_of_money?
living_expenses = money - credit_card - ...
living_expenses < 100 ? true : false
end
end
如果我其他地方也需要使用到living_expenses
時,就要重複寫同一行living_expenses = money - credit_card - ...
,或許這不是什麼大問題,但很麻煩... 更可怕的事情是在整份檔案我用了好幾十個living_expenses
並且我的計算方式需要更改
那麼將living_expenses
從區域變數改成@living_expenses
實體變數,然後將計算放到initialize
中呢? 感覺可行哦~?
回過頭來,仔細想想...若是這個類別自始至終都不需要這個數據,而卻每次new
時都會多計算這段程式,感覺有些多此一舉呢
我想我們還是模仿Ruby的attr_resder
的概念吧!
def living_expenses
@account_balances ||= money - credit_card - ...
end
當需要修改計算方法,我們就只要改living_expenses
就可以。所以我們仰賴living_expenses
方法(行為),而非變數(資料)。 此外,在這段程式碼中,只有在第一次使用living_expenses
會進行計算並存入@account_balances
之中,當下次呼叫時,就不需重新計算,而是直接找到已經儲存在@account_balances
的數值,這整個流程就是隱藏實例變數。
那麼什麼是隱藏資料結構呢? 這時我們假設傳入的money
不再是一個數字,而是money = [salary, red_envelope, refund]
的資料結構時呢...?@account_balances ||= money - credit_card - ...
就會變成@account_balances ||= money[0] - credit_card - ...
,等下一個人來看發現money[0]
!
八成滿臉疑惑吧~
所以我們就改成
class Ken
attr_reader :salary
def initialize(money)
@salary = money[0]
end
def lack_of_money?
living_expenses < 100 ? true : false
end
private
def living_expenses
@account_balances ||= salary - credit_card - ...
end
end
而這樣簡單的轉換就是隱藏資料結構,是不是變得比要好理解些呢!
話說昨天提到今天也要奉上依賴關係但好像篇幅太大了... 決定明日再來分享好囉!
感謝大家~ 如有問題,再煩請大家指教!